package com.wasai.dao;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;

import com.wasai.domain.BaseDomain;

/**
 * Data access object (DAO) for domain model.
 * 
 * @param <T>
 */
@Repository
public class BaseDAO<T extends BaseDomain> extends HibernateDaoSupport {

	/**
	 * 
	 */
	private static final Logger log = LoggerFactory.getLogger(BaseDAO.class);

	/**
	 * 
	 * @param instanceClass
	 * @return
	 */
	public final Long countAll(final Class<T> instanceClass) {
		log.debug("counting all Domain instances");
		try {
			final Criteria criteria = this.getSession()
					.createCriteria(instanceClass)
					.setProjection(Projections.rowCount());
			criteria.setCacheable(true);
			final Long result = (Long) criteria.uniqueResult();
			log.debug("count all Domain instances, result size: " + result);
			return result;
		} catch (final RuntimeException re) {
			log.error("count all Domain instances failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param dc
	 * @return
	 */
	public final Long countByCriteria(final DetachedCriteria dc) {
		log.debug("counting by Criteria");
		try {
			dc.setProjection(Projections.rowCount());
			final Criteria criteria = dc.getExecutableCriteria(this
					.getSession());
			criteria.setCacheable(true);
			final Long result = (Long) criteria.uniqueResult();
			log.debug("count by Criteria, result: " + result);
			return result;
		} catch (final RuntimeException re) {
			log.debug("count by Criteria failed");
			throw re;
		}
	}

	/**
	 * 
	 * @param instance
	 * @return
	 */
	public final Long countbyExample(final T instance) {
		log.debug("counting Domain instance by example");
		try {
			final Criteria criteria = this.getSession()
					.createCriteria(instance.getClass())
					.add(Example.create(instance))
					.setProjection(Projections.rowCount());
			criteria.setCacheable(true);
			final Long result = (Long) criteria.uniqueResult();
			log.debug("count by example successful, result : " + result);
			return result;
		} catch (final RuntimeException re) {
			log.error("count by example failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param persistentInstance
	 */
	public final void delete(final T persistentInstance) {
		log.debug("deleting Domain instance");
		try {
			this.getSession().delete(persistentInstance);
			log.debug("delete successful");
		} catch (final RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param instanceClass
	 * @param firstResult
	 * @param maxResult
	 * @param orders
	 * @return
	 */
	public final List<T> findAll(final Class<T> instanceClass,
			final int firstResult, final int maxResults, final Order[] orders) {
		log.debug("finding all Domain instances");
		try {
			final Criteria criteria = this.getSession().createCriteria(
					instanceClass);
			criteria.setCacheable(true);
			if ((firstResult >= 0) && (maxResults >= 0)) {
				criteria.setFirstResult(firstResult);
				criteria.setMaxResults(maxResults);
			}
			if ((orders != null) && (orders.length > 0)) {
				for (final Order order : orders) {
					criteria.addOrder(order);
				}
			}
			@SuppressWarnings("unchecked")
			final List<T> results = criteria.list();
			log.debug("find all Domain instances, result size: "
					+ results.size());
			return results;
		} catch (final RuntimeException re) {
			log.error("find all failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param criteria
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final List<T> findByCriteria(final DetachedCriteria dc,
			final int firstResult, final int maxResults, final Order[] orders) {
		log.debug("finding by Criteria");
		try {
			final Criteria criteria = dc.getExecutableCriteria(this
					.getSession());
			criteria.setCacheable(true);
			List<T> result;
			if ((firstResult >= 0) && (maxResults >= 0)) {
				criteria.setFirstResult(firstResult);
				criteria.setMaxResults(maxResults);
			}
			if ((orders != null) && (orders.length > 0)) {
				for (final Order order : orders) {
					criteria.addOrder(order);
				}
			}
			result = criteria.list();
			log.debug("find by Criteria, result size: " + result.size());
			return result;
		} catch (final RuntimeException re) {
			log.debug("finding by Criteria failed");
			throw re;
		}
	}

	/**
	 * 
	 * @param instance
	 * @param firstResult
	 * @param maxResults
	 * @param orders
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final List<T> findByExample(final T instance, final int firstResult,
			final int maxResults, final Order[] orders) {
		log.debug("finding Domain instance by example");
		try {
			final Criteria criteria = this.getSession()
					.createCriteria(instance.getClass())
					.add(Example.create(instance));
			criteria.setCacheable(true);
			if ((firstResult >= 0) && (maxResults >= 0)) {
				criteria.setFirstResult(firstResult);
				criteria.setMaxResults(maxResults);
			}
			if ((orders != null) && (orders.length > 0)) {
				for (final Order order : orders) {
					criteria.addOrder(order);
				}
			}
			final List<T> results = criteria.list();
			log.debug("find by example successful, result size: "
					+ results.size());
			return results;
		} catch (final RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param dc
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final T getByCriteria(final DetachedCriteria dc) {
		log.debug("getting by Criteria");
		try {
			final Criteria criteria = dc.getExecutableCriteria(this
					.getSession());
			criteria.setCacheable(true);
			T result = (T) criteria.uniqueResult();
			log.debug("get by Criteria, successful");
			return result;
		} catch (final RuntimeException re) {
			log.debug("finding by Criteria failed");
			throw re;
		}
	}

	/**
	 * 
	 * @param instance
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final T getByExample(final T example) {
		log.debug("getting Domain instance by example");
		try {
			final Criteria criteria = this.getSession()
					.createCriteria(example.getClass())
					.add(Example.create(example));
			criteria.setCacheable(true);
			final T result = (T) criteria.uniqueResult();
			log.debug("get by example successful");
			return result;
		} catch (final RuntimeException re) {
			log.error("get by example failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param instanceClass
	 * @param id
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final T getById(final Class<? extends BaseDomain> instanceClass,
			final java.lang.Integer id) {
		log.debug("getting Domain instance with id: " + id);
		try {
			final T instance = (T) this.getSession().get(instanceClass, id);
			if (instance != null) {
				log.debug("getting Domain instance successful");
			}
			return instance;
		} catch (final RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param detachedInstance
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final T merge(final T detachedInstance) {
		log.debug("merging Domain instance");
		try {
			final T result = (T) this.getSession().merge(detachedInstance);
			log.debug("merge successful");
			return result;
		} catch (final RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param transientInstance
	 */
	public final void refresh(final T transientInstance) {
		log.debug("refreshing Domain instance");
		try {
			this.getSession().refresh(transientInstance);
			log.debug("refresh successful");
		} catch (final RuntimeException re) {
			log.error("refresh failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param transientInstance
	 */
	public final void save(final T transientInstance) {
		log.debug("saving Domain instance");
		try {
			this.getSession().save(transientInstance);
			log.debug("save successful");
		} catch (final RuntimeException re) {
			log.error("save failed", re);
			throw re;
		}
	}

	/**
	 * 
	 * @param transientInstance
	 */
	public final void update(final T transientInstance) {
		log.debug("updateing Domain instance");
		try {
			this.getSession().update(transientInstance);
			log.debug("update successful");
		} catch (final RuntimeException re) {
			log.error("update failed", re);
			throw re;
		}
	}
}
